/****************************************************************************
 * Elisa 1.0                                                                *
 *--------------------------------------------------------------------------*
 * Author: Laurent Granvilliers, Vincent Sorin                              *
 * Copyright (c) 2004 Laboratoire d'Informatique de Nantes Atlantique       *
 *--------------------------------------------------------------------------*
 * Elisa is distributed WITHOUT ANY WARRANTY. Read the associated           *
 * COPYRIGHT file for more details.                                         *
 *--------------------------------------------------------------------------*
 * elisa_interval.h                                                         *
 ****************************************************************************/

/**
 * @file elisa_interval.h
 * @brief The interval class
 */

/**
 * @defgroup Interval Interval
 * @brief Interval classes
 */


#ifndef _ELISA_INTERVAL_H
#define _ELISA_INTERVAL_H

#include <iostream>
#include <iomanip>
#include <string>
#include <cmath>
#include "elisa_defs.h"
#include "elisa_domain.h"


#ifdef NEW_INTERVAL

#include "elisa_interval_gaol.h"

#else

#include "elisa_icode_gaol.h"

NAMESPACE_ELISA

Bound nextUp(const Bound & r);
double toDouble(const Bound & r);
int toInt(const Bound & r);

/// Machine number before r
//static inline Bound PrevBound(const Bound & r)
Bound nextDown(const Bound & r);

/**
 * @ingroup Interval
 * @class Interval
 * @brief Interval type used in the constraint library
 */
class Interval : public Domain
{
public:

	/// Type for interval bounds
	typedef elisa::Bound Bound;

	/// @name Constructors
	//@{
	Interval ()
	{
		IcodeTraits::cons(_I, 0.0, 0.0);
	}

	Interval (Interval l, Interval u)
	{
		IcodeTraits::set(_I, l._I);
		this->setUnion(u);
	}

	Interval (double l, double u)
	{
		IcodeTraits::cons(_I, l, u);
	}

	Interval (double x)
	{
		IcodeTraits::cons(_I, x, x);
	}

	Interval (int l, int u)
	{
		IcodeTraits::cons(_I, double (l), double (u));
	}

	Interval (int x)
	{
		IcodeTraits::cons(_I, double (x), double (x));
	}

	Interval (int l, double u)
	{
		IcodeTraits::cons(_I, double (l), u);
	}

	Interval (double l, int u)
	{
		IcodeTraits::cons(_I, l, double (u));
	}

	Interval (const std::string & l, const std::string & u)
	{
		IcodeTraits::cons(_I, l, u);
	}

	Interval (const std::string & x)
	{
		IcodeTraits::cons(_I, x, x);
	}

	Interval (const Interval & I) : Domain(I)
	{
		IcodeTraits::set(_I, I._I);
	}

	Interval (const Icode & I)
	{
		IcodeTraits::set(_I, I);
	}

#ifndef HAVE_DOUBLE_BOUND
	Interval(const Bound & x, const Bound & y)
	{
		IcodeTraits::cons(_I, x, y);
	}
#endif // HAVE_DOUBLE_BOUND
	   //@}

	/// Destructor
	~Interval ()
	{
		IcodeTraits::del(_I);
	}

	/// Copy
	Interval &
	operator=(const Interval & I)
	{
		IcodeTraits::set(_I, I._I);
		return *this;
	}

	/// *this := intersection(I,*this); returns true if *this is modified
	bool setIntersect(const Interval & I)
	{
		Icode save;
		IcodeTraits::set(save, _I);
		IcodeTraits::set(_I, IcodeTraits::intersect(_I, I._I));
		return !IcodeTraits::isSetEqual(_I, save);
	}

	/// *this := unio(I,*this); returns true if *this is modified
	bool setUnion(const Interval & I)
	{
		Icode save;
		IcodeTraits::set(save, _I);
		IcodeTraits::set(_I, IcodeTraits::unio(_I, I._I));
		return !IcodeTraits::isSetEqual(_I, save);
	}

	/// *this := hull(I,*this)
	void hull(const Interval & I)
	{
		_I = IcodeTraits::hull(_I, I._I);
	}

	/// *this := empty interval
	void setEmpty()
	{
		IcodeTraits::setEmpty(_I);
	}

	/// @name Bound operations
	//@{
	/// Inferior bound
	Bound inf() const
	{
		return IcodeTraits::inf(_I);
	}
	/// Superior bound
	Bound sup() const
	{
		return IcodeTraits::sup(_I);
	}
	//@}

	/// @name Machine number operations
	//@{
	/// Approximation of the midpoint
	Bound mid() const
	{
		return IcodeTraits::mid(_I);
	}

	/// Approximation of the integer midpoint
	Bound midInt() const
	{
		return IcodeTraits::midInteger(_I);
	}

	/// Returns the mignitude (minimum of absolute values)
	Bound mig() const
	{
		return IcodeTraits::mig(_I);
	}

	/// Returns the magnitude (maximum of absolute values)
	Bound mag() const
	{
		return IcodeTraits::mag(_I);
	}
	//@}

	/// Width of [a,b] = (b-a) upward rounded
	Bound width() const
	{
		return IcodeTraits::width(_I);
	}

	/// THIS = [a,b] := [ceil(a),floor(b)]
	Interval toInt() const
	{
		return(IcodeTraits::toInteger(_I));
	}

	/// Returns true if *this is a point interval [a,a]
	bool isPoint() const
	{
		return IcodeTraits::isPoint(_I);
	}

	/// Returns true if *this is equal to the interval [0,0]
	bool isZero() const
	{
		return IcodeTraits::isZero(_I);
	}

	/// Returns true if *this contains at most two machine numbers
	bool isCanonical() const
	{
		return IcodeTraits::isCanonical(_I);
	}

	/// Returns true if *this is an integer interval
	bool isAnInt() const
	{
		return IcodeTraits::isAnInt(_I);
	}

	/// Returns true if the width of *this is smaller than or equal to p
	bool isPrecise(double p) const
	{
		Bound w = width();
		return w <= p;
	}

	/// Returns true if *this contains at most n machine numbers
	bool isPrecise(int n) const
	{
		return IcodeTraits::isPrecise(_I, n);
	}

	/// Returns true if *this intersects I
	bool overlaps(const Interval & I) const
	{
		return IcodeTraits::overlaps(_I, I._I);
	}

	/// *this := *this intersection I and returns false if *this becomes empty
	//bool intersect(const Interval & I)
	Interval& intersect(const Interval & I)
	{
		Interval J(IcodeTraits::intersect(_I, I._I));
		IcodeTraits::set(_I, J._I);
		//return !isEmpty();
		return *this;
	}

	/// Returns true if *this is empty
	bool isEmpty() const
	{
		return IcodeTraits::isEmpty(_I);
	}

	/**
 	 * @name Comparisons (set, possibly, certainly)
 	 * @see elisa::interval_traits
 	 */
	//@{
	bool
	operator==(const Interval & I) const
	{
		return IcodeTraits::isSetEqual(_I, I._I);
	}

	bool
	operator!=(const Interval & I) const
	{
		return !(IcodeTraits::isSetEqual(_I, I._I));
	}

	bool isSetEqual(const Interval & I) const
	{
		return IcodeTraits::isSetEqual(_I, I._I);
	}

	bool isPossiblyEqual(const Interval & I) const
	{
		return IcodeTraits::isPossiblyEqual(_I, I._I);
	}

	bool isCertainlyEqual(const Interval & I) const
	{
		return IcodeTraits::isCertainlyEqual(_I, I._I);
	}

	bool isSetLessThan(const Interval & I) const
	{
		return IcodeTraits::isSetLessThan(_I, I._I);
	}

	bool isPossiblyLessThan(const Interval & I) const
	{
		return IcodeTraits::isPossiblyLessThan(_I, I._I);
	}

	bool isCertainlyLessThan(const Interval & I) const
	{
		return IcodeTraits::isCertainlyLessThan(_I, I._I);
	}

	//@}

	typedef std::pair<Interval, Interval> Pair;

	/// Bisects *this in I union J
	//void split(Interval & I, Interval & J) const
	Pair split() const
	{
		Interval I, J;
		IcodeTraits::split(_I, I._I, J._I);
		return Pair(I, J);
	}

	/**
 	 * Bisects *this in I union J such that the bounds of I and J
 	 * are integer values and no integer is shared by the intervals
 	 */
	void splitToInteger(Interval & I, Interval & J) const
	{
		IcodeTraits::splitToInteger(_I, I._I, J._I);
	}

	/// @name Arithmetic operations
	//@{
	Interval &
	operator+=(const Interval & I)
	{
		IcodeTraits::addeq(_I, I._I);
		return *this;
	}

	Interval &
	operator-=(const Interval & I)
	{
		IcodeTraits::subeq(_I, I._I);
		return *this;
	}

	Interval &
	operator*=(const Interval & I)
	{
		IcodeTraits::muleq(_I, I._I);
		return *this;
	}

	Interval &
	operator/=(const Interval & I)
	{
		IcodeTraits::diveq(_I, I._I);
		return *this;
	}

	Interval
	operator+(const Interval & I) const
	{
		return Interval(IcodeTraits::add(_I, I._I));
	}

	Interval
	operator*(const Interval & I) const
	{
		return Interval(IcodeTraits::mul(_I, I._I));
	}

	Interval
	operator-(const Interval & I) const
	{
		return Interval(IcodeTraits::sub(_I, I._I));
	}

	Interval
	operator-() const
	{
		return Interval(IcodeTraits::unarysub(_I));
	}

	Interval
	operator/(const Interval & I) const
	{
		return Interval(IcodeTraits::div(_I, I._I));
	}

	Interval sqr() const
	{
		return Interval(IcodeTraits::sqr(_I));
	}

	Interval sqrt() const
	{
		return Interval(IcodeTraits::sqrt(_I));
	}

	Interval pow(int n) const
	{
		return Interval(IcodeTraits::pow(_I, n));
	}

	Interval min(const Interval & I) const
	{
		return Interval(IcodeTraits::min(_I, I._I));
	}

	Interval max(const Interval & I) const
	{
		return Interval(IcodeTraits::max(_I, I._I));
	}

	Interval abs() const
	{
		return Interval(IcodeTraits::abs(_I));
	}

	Interval nth_root(int n) const
	{
		return Interval(IcodeTraits::nth_root(_I, n));
	}

	Interval log() const
	{
		return Interval(IcodeTraits::log(_I));
	}

	Interval exp() const
	{
		return Interval(IcodeTraits::exp(_I));
	}

	Interval cos() const
	{
		return Interval(IcodeTraits::cos(_I));
	}

	Interval acos() const
	{
		return Interval(IcodeTraits::acos(_I));
	}

	Interval sin() const
	{
		return Interval(IcodeTraits::sin(_I));
	}

	Interval asin() const
	{
		return Interval(IcodeTraits::asin(_I));
	}

	Interval tan() const
	{
		return Interval(IcodeTraits::tan(_I));
	}

	Interval atan() const
	{
		return Interval(IcodeTraits::atan(_I));
	}

	Interval cosh() const
	{
		return Interval(IcodeTraits::cosh(_I));
	}

	Interval acosh() const
	{
		return Interval(IcodeTraits::acosh(_I));
	}

	Interval sinh() const
	{
		return Interval(IcodeTraits::sinh(_I));
	}

	Interval asinh() const
	{
		return Interval(IcodeTraits::asinh(_I));
	}

	Interval tanh() const
	{
		return Interval(IcodeTraits::tanh(_I));
	}

	Interval atanh() const
	{
		return Interval(IcodeTraits::atanh(_I));
	}
	//@}

	/**
 	 * @name Output on a stream
 	 */
	//@{
	/// Output of *this on os
	void print(std::ostream & os) const
	{
		IcodeTraits::print(os, _I);
	}

	static inline void SetOutputDigits(int n)
	{
		IcodeTraits::setOutputDigits(n);
	}
	//@}

	/// @name Constants
	//@{
	static const Interval Universe;
	static const Interval Zero;
	static const Interval EmptySet;
	static const Interval Pi;
	//@}


	bool isDifferent(const Domain & d) const
	{
		Interval *i = dynamic_cast<Interval *>(const_cast<Domain *>(&d));

		if (i != 0) {
			return((*i) != (*this));
		} else {
			return true;
		}
	}

	double getWidth() const
	{
		return toDouble(width());
	}

private:
	Icode _I;


	//@name Relation Arithmetic
	//@{
	friend Interval asin_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::asin_rel(J._I, I._I));
	}

	friend Interval acos_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::acos_rel(J._I, I._I));
	}

	friend Interval atan_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::atan_rel(J._I, I._I));
	}

	friend Interval asinh_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::asinh_rel(J._I, I._I));
	}

	friend Interval acosh_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::acosh_rel(J._I, I._I));
	}

	friend Interval atanh_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::atanh_rel(J._I, I._I));
	}

	friend Interval sqrt_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::sqrt_rel(J._I, I._I));
	}

	friend Interval nth_root_rel(const Interval & J, int n, const Interval & I)
	{
		return Interval(IcodeTraits::nth_root_rel(J._I, n, I._I));
	}

	friend Interval invabs_rel(const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::invabs_rel(J._I, I._I));
	}

	friend Interval div_rel(const Interval & K, const Interval & J, const Interval & I)
	{
		return Interval(IcodeTraits::div_rel(K._I, J._I, I._I));
	}
	//@}
};

/** @relates Interval
 * @brief I + n
 */
inline elisa::Interval
operator+(const elisa::Interval & I, const int & n)
{
	return I + elisa::Interval(n);
}

/** @relates Interval
 * @brief n + I
 */
inline elisa::Interval
operator+(const int & n, const elisa::Interval & I)
{
	return I + elisa::Interval(n);
}

/** @relates Interval
 * @brief I + x
 */
inline elisa::Interval
operator+(const elisa::Interval & I, const double & x)
{
	return I + elisa::Interval(x);
}

/** @relates Interval
 * @brief x + I
 */
inline elisa::Interval
operator+(const double & x, const elisa::Interval & I)
{
	return I + elisa::Interval(x);
}

/** @relates Interval
 * @brief I * n
 */
inline elisa::Interval
operator*(const elisa::Interval & I, const int & n)
{
	return I *elisa::Interval(n);
}

/** @relates Interval
 * @brief n * I
 */
inline elisa::Interval
operator*(const int & n, const elisa::Interval & I)
{
	return I *elisa::Interval(n);
}

/** @relates Interval
 * @brief I * x
 */
inline elisa::Interval
operator*(const elisa::Interval & I, const double & x)
{
	return I *elisa::Interval(x);
}

/** @relates Interval
 * @brief x - I
 */
inline elisa::Interval
operator*(const double & x, const elisa::Interval & I)
{
	return I *elisa::Interval(x);
}

/** @relates Interval
 * @brief I - n
 */
inline elisa::Interval
operator-(const elisa::Interval & I, const int & n)
{
	return I - elisa::Interval(n);
}

/** @relates Interval
 * @brief n - I
 */
inline elisa::Interval operator-(const int & n, const elisa::Interval & I)
{
	return elisa::Interval(n) - I;
}

/** @relates Interval
 * @brief I - x
 */
inline elisa::Interval operator-(const elisa::Interval & I, const double & x)
{
	return I - elisa::Interval(x);
}

/** @relates Interval
 * @brief x - I
 */
inline elisa::Interval operator-(const double & x, const elisa::Interval & I)
{
	return elisa::Interval(x) - I;
}

/** @relates Interval
 * @brief I / n
 */
inline elisa::Interval operator/(const elisa::Interval & I, const int & n)
{
	return I / elisa::Interval(n);
}

/** @relates Interval
 * @brief n / I
 */
inline elisa::Interval operator/(const int & n, const elisa::Interval & I)
{
	return elisa::Interval(n) / I;
}

/** @relates Interval
 * @brief I / x
 */
inline elisa::Interval operator/(const elisa::Interval & I, const double & x)
{
	return I / elisa::Interval(x);
}

/** @relates Interval
 * @brief x / I
 */
inline elisa::Interval
operator/(const double & x, const elisa::Interval & I)
{
	return elisa::Interval(x) / I;
}


/** @relates Interval
 * @brief I power 2
 */
inline elisa::Interval sqr(const elisa::Interval & I)
{
	return I.sqr();
}

/** @relates Interval
 * @brief sqrt(I)
 */
inline elisa::Interval sqrt(const elisa::Interval & I)
{
	return I.sqrt();
}

/** @relates Interval
 * @brief I power n
 */
inline elisa::Interval pow(const elisa::Interval & I, int n)
{
	return I.pow(n);
}

/** @relates Interval
 * @brief min(I,J)
 */
inline elisa::Interval min(const elisa::Interval & I, const elisa::Interval & J)
{
	return I.min(J);
}

/** @relates Interval
 * @brief max(I,J)
 */
inline elisa::Interval max(const elisa::Interval & I, const elisa::Interval & J)
{
	return I.max(J);
}


/** @relates Interval
 * @brief abs(I)
 */
inline elisa::Interval abs(const elisa::Interval & I)
{
	return I.abs();
}


/** @relates Interval
 * @brief log(I)
 */
inline elisa::Interval log(const elisa::Interval & I)
{
	return I.log();
}

/** @relates Interval
 * @brief exp(I)
 */
inline elisa::Interval exp(const elisa::Interval & I)
{
	return I.exp();
}

/** @relates Interval
 * @brief cos(I)
 */
inline elisa::Interval cos(const elisa::Interval & I)
{
	return I.cos();
}

/** @relates Interval
 * @brief acos(I)
 */
inline elisa::Interval acos(const elisa::Interval & I)
{
	return I.acos();
}

/** @relates Interval
 * @brief sin(I)
 */
inline elisa::Interval sin(const elisa::Interval & I)
{
	return I.sin();
}

/** @relates Interval
 * @brief asin(I)
 */
inline elisa::Interval asin(const elisa::Interval & I)
{
	return I.asin();
}

/** @relates Interval
 * @brief tan(I)
 */
inline elisa::Interval tan(const elisa::Interval & I)
{
	return I.tan();
}

/** @relates Interval
 * @brief atan(I)
 */
inline elisa::Interval atan(const elisa::Interval & I)
{
	return I.atan();
}

/** @relates Interval
 * @brief cos(I)
 */
inline elisa::Interval cosh(const elisa::Interval & I)
{
	return I.cosh();
}

/** @relates Interval
 * @brief acos(I)
 */
inline elisa::Interval acosh(const elisa::Interval & I)
{
	return I.acosh();
}

/** @relates Interval
 * @brief sin(I)
 */
inline elisa::Interval sinh(const elisa::Interval & I)
{
	return I.sinh();
}

/** @relates Interval
 * @brief asin(I)
 */
inline elisa::Interval asinh(const elisa::Interval & I)
{
	return I.asinh();
}

/** @relates Interval
 * @brief tan(I)
 */
inline elisa::Interval tanh(const elisa::Interval & I)
{
	return I.tanh();
}

/** @relates Interval
 * @brief atan(I)
 */
inline elisa::Interval atanh(const elisa::Interval & I)
{
	return I.atanh();
}


/// Machine number following r
//static inline Bound NextBound(const Bound & r)
inline Bound nextUp(const Bound & r)
{
	return IcodeTraits::next(r);
}

/// Conversion from Bound to double
inline double toDouble(const Bound & r)
{
	return IcodeTraits::convert(r);
}
inline int toInt(const Bound & r)
{
	return std::floor(r);
}

/// Machine number before r
//static inline Bound PrevBound(const Bound & r)
inline Bound nextDown(const Bound & r)
{
	return IcodeTraits::prev(r);
}

END_NAMESPACE_ELISA


/** @relates Interval
* @brief Output of I on os
*/
inline std::ostream &
operator<<(std::ostream & os,
		   const elisa::Interval & I)
{
	I.print(os);
	return os;
}

#endif

void printAsRect(std::ostream & os, const elisa::Interval & x, const elisa::Interval & y);

#endif // ELISA_INTERVAL_H
